// Inferno utils/5l/asm。c 
// https:
// 
// 版权所有©1994-1999朗讯科技有限公司。保留所有权利。
// 版权所有©1995-1997 C H Forsyth(forsyth@terzarima.net)
// 部分版权©1997-1999维塔诺瓦有限公司
// 部分版权©2000-2007维塔诺瓦控股有限公司（www.vitanuova.com）
// 部分版权©20042006布鲁斯·埃利斯
// 修订版权©2000-2007朗讯科技有限公司及其他
// 部分版权©2009 Go作者。保留所有权利。
// 
// 特此免费授予获得本软件及相关文档文件（以下简称“软件”）副本
// 的任何人在本软件中不受限制地处理
// 的权利，包括但不限于
// 使用、复制、修改、合并、发布、分发、再许可的权利，和/或出售本软件的
// 副本，并允许提供本软件的人员在以下条件下这样做：
// 
// 上述版权声明和本许可声明应包含在
// 本软件的所有副本或大部分中。
// 
// 本软件按“原样”提供，不提供任何形式的明示或暗示担保，包括但不限于适销性担保、
// 适用于特定用途和非侵权性担保。在任何情况下，
// 作者或版权持有人均不对任何索赔、损害赔偿或其他
// 责任承担责任，无论是合同诉讼、侵权诉讼还是其他诉讼，均由
// 因本软件或本软件的使用或其他交易引起，或与本软件或本软件的使用或其他交易有关。

package ppc64

import (
	"cmd/internal/objabi"
	"cmd/internal/sys"
	"cmd/link/internal/ld"
	"cmd/link/internal/loader"
	"cmd/link/internal/sym"
	"debug/elf"
	"encoding/binary"
	"fmt"
	"log"
	"strings"
)

func genplt(ctxt *ld.Link, ldr *loader.Loader) {
	// ppc64 ABI PLT的概念与其他
	// 体系结构类似，但布局却截然不同。当我们看到R_PPC64_REL24重新定位到动态符号
	// （表示呼叫需要通过PLT）时，我们
	// 生成最多三个存根并保留一个PLT插槽。
	// 
	// 1）呼叫地点为bl x；不适用（如果
	// 适用于bl）。我们把它改写成bl x_存根；ld 
	// r2,24（r1）。ld是必需的，因为x_存根将把
	// r2（TOC指针）保存在24（r1）（“TOC保存槽”）。
	// 
	// 2）我们在内存中为指针保留空间。plt部分（每个引用的动态函数一次
	// ）。plt是仅由动态链接器填充的数据
	// 部分（更像其他架构上的
	// /.plt.got）。最初，
	// 动态链接器将用指向相应
	// 的指针填充每个插槽x@plt入口点。
	// 
	// 3）我们生成“调用存根”x_存根（每个动态
	// 函数/对象文件对一次）。这将TOC保存在
	// TOC保存槽中，从x读取函数指针。plt 
	// slot并像任何其他全局入口点一样调用它
	// （包括将r12设置为函数地址）。我们生成“符号解析器存根”x@plt（每个
	// 动态功能一次）。这只是glink 
	// 解析器存根的一个分支。我们生成glink解析器存根（仅一次）。这个
	// 计算我们通过哪个符号解析器存根，
	// 通过动态链接器
	// 提供的指针调用动态解析器。这将修补这个问题。plt slot to 
	// 直接指向函数，以便将来的调用直接从调用存根到实际函数，然后调用函数。

	// 注意：我们有可能使ppc64更接近其他
	// 体系结构：ppc64。plt就像。plt。上了其他
	// 平台和ppc64。格林克就像。其他
	// 平台上的plt。

	// 查找所有引用动态
	// 导入的R_PPC64_REL24重新定位。为这些符号保留PLT条目，并生成呼叫存根。电话存根需要住在里面。text，
	// 这就是为什么我们需要这么早通过这个考试的原因。
	// 
	// 这假设来自ABI的“case 1”，调用方需要
	// us来保存和恢复TOC指针。
	var stubs []loader.Sym
	for _, s := range ctxt.Textp {
		relocs := ldr.Relocs(s)
		for i := 0; i < relocs.Count(); i++ {
			r := relocs.At(i)
			if r.Type() != objabi.ElfRelocOffset+objabi.RelocType(elf.R_PPC64_REL24) || ldr.SymType(r.Sym()) != sym.SDYNIMPORT {
				continue
			}

			// 保留PLT条目并生成符号
			// 解析器
			addpltsym(ctxt, ldr, r.Sym())

			// 生成调用存根。需要注意的是，我们正在使用与父符号相同的版本查找存根，以便symtoc（）选择正确的。TOC。符号
			// 处理存根时。在旧版本的链接器
			// 中，这是通过设置存根完成的。外部到父级，但
			// 如果存根最初有正确的版本，则不需要这样做。
			n := fmt.Sprintf("%s.%s", ldr.SymName(s), ldr.SymName(r.Sym()))
			stub := ldr.CreateSymForUpdate(n, ldr.SymVersion(s))
			if stub.Size() == 0 {
				stubs = append(stubs, stub.Sym())
				gencallstub(ctxt, ldr, 1, stub, r.Sym())
			}

			// 更新重新定位以使用呼叫存根
			r.SetSym(stub.Sym())

			// 使符号可写，以便我们可以修复toc。
			su := ldr.MakeSymbolUpdater(s)
			su.MakeWritable()
			p := su.Data()

			// 检查toc恢复插槽（nop），并替换为toc恢复。
			var nop uint32
			if len(p) >= int(r.Off()+8) {
				nop = ctxt.Arch.ByteOrder.Uint32(p[r.Off()+4:])
			}
			if nop != 0x60000000 {
				ldr.Errorf(s, "Symbol %s is missing toc restoration slot at offset %d", ldr.SymName(s), r.Off()+4)
			}
			const o1 = 0xe8410018 // ld r2,24（r1）
			ctxt.Arch.ByteOrder.PutUint32(p[r.Off()+4:], o1)
		}
	}
	// 将调用存根放在开头（而不是结尾）。
	// 因此，在解决对存根调用的重新定位时，
	// 地址是已知的，必要时可以插入蹦床
	// 。
	ctxt.Textp = append(stubs, ctxt.Textp...)
}

func genaddmoduledata(ctxt *ld.Link, ldr *loader.Loader) {
	initfunc, addmoduledata := ld.PrepareAddmoduledata(ctxt)
	if initfunc == nil {
		return
	}

	o := func(op uint32) {
		initfunc.AddUint32(ctxt.Arch, op)
	}

	// 亚的斯亚贝巴r2，r12。TOC-func@ha
	toc := ctxt.DotTOC[0]
	rel1, _ := initfunc.AddRel(objabi.R_ADDRPOWER_PCREL)
	rel1.SetOff(0)
	rel1.SetSiz(8)
	rel1.SetSym(toc)
	o(0x3c4c0000)
	// addi r2，r2。TOC-func@l
	o(0x38420000)
	// mflr r31 
	o(0x7c0802a6)
	// stdu r31，-32（r1）
	o(0xf801ffe1)
	// addis r3，r2，本地。moduledata@got@ha 
	var tgt loader.Sym
	if s := ldr.Lookup("local.moduledata", 0); s != 0 {
		tgt = s
	} else if s := ldr.Lookup("local.pluginmoduledata", 0); s != 0 {
		tgt = s
	} else {
		tgt = ldr.LookupOrCreateSym("runtime.firstmoduledata", 0)
	}
	rel2, _ := initfunc.AddRel(objabi.R_ADDRPOWER_GOT)
	rel2.SetOff(int32(initfunc.Size()))
	rel2.SetSiz(8)
	rel2.SetSym(tgt)
	o(0x3c620000)
	// ld r3，本地。moduledata@got@l（r3）
	o(0xe8630000)
	// bl运行时。addmoduledata 
	rel3, _ := initfunc.AddRel(objabi.R_CALLPOWER)
	rel3.SetOff(int32(initfunc.Size()))
	rel3.SetSiz(4)
	rel3.SetSym(addmoduledata)
	o(0x48000001)
	// nop 
	o(0x60000000)
	// ld r31,0（r1）
	o(0xe8010000)
	// mtlr r31 
	o(0x7c0803a6)
	// addi r1,r1,32 
	o(0x38210020)
	// blr 
	o(0x4e800020)
}

func gentext(ctxt *ld.Link, ldr *loader.Loader) {
	if ctxt.DynlinkingGo() {
		genaddmoduledata(ctxt, ldr)
	}

	if ctxt.LinkMode == ld.LinkInternal {
		genplt(ctxt, ldr)
	}
}

// 在存根中构造一个调用存根，通过其PLT 
// 条目调用符号targ。
func gencallstub(ctxt *ld.Link, ldr *loader.Loader, abicase int, stub *loader.SymbolBuilder, targ loader.Sym) {
	if abicase != 1 {
		// 如果我们看到R_PPC64_to save或R_PPC64_REL24_NOTOC 
		// 重新安置，我们需要实施案例2和案例3。
		log.Fatalf("gencallstub only implements case 1 calls")
	}

	plt := ctxt.PLT

	stub.SetType(sym.STEXT)

	// 在TOC保存槽中保存TOC指针
	stub.AddUint32(ctxt.Arch, 0xf8410018) // std r2,24（r1）

	// 从PLT加载函数指针。
	rel, ri1 := stub.AddRel(objabi.R_POWER_TOC)
	rel.SetOff(int32(stub.Size()))
	rel.SetSiz(2)
	rel.SetAdd(int64(ldr.SymPlt(targ)))
	rel.SetSym(plt)
	if ctxt.Arch.ByteOrder == binary.BigEndian {
		rel.SetOff(rel.Off() + int32(rel.Siz()))
	}
	ldr.SetRelocVariant(stub.Sym(), int(ri1), sym.RV_POWER_HA)
	stub.AddUint32(ctxt.Arch, 0x3d820000) // 亚的斯亚贝巴r12，r2，targ@plt@toc@ha

	rel2, ri2 := stub.AddRel(objabi.R_POWER_TOC)
	rel2.SetOff(int32(stub.Size()))
	rel2.SetSiz(2)
	rel2.SetAdd(int64(ldr.SymPlt(targ)))
	rel2.SetSym(plt)
	if ctxt.Arch.ByteOrder == binary.BigEndian {
		rel2.SetOff(rel2.Off() + int32(rel2.Siz()))
	}
	ldr.SetRelocVariant(stub.Sym(), int(ri2), sym.RV_POWER_LO)
	stub.AddUint32(ctxt.Arch, 0xe98c0000) // ld r12，targ@plt@toc@l（r12）

	// 跳转到加载的指针
	stub.AddUint32(ctxt.Arch, 0x7d8903a6) // mtctr r12 
	stub.AddUint32(ctxt.Arch, 0x4e800420) // bctr 
}

func adddynrel(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, s loader.Sym, r loader.Reloc, rIdx int) bool {
	if target.IsElf() {
		return addelfdynrel(target, ldr, syms, s, r, rIdx)
	} else if target.IsAIX() {
		return ld.Xcoffadddynrel(target, ldr, syms, s, r, rIdx)
	}
	return false
}

func addelfdynrel(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, s loader.Sym, r loader.Reloc, rIdx int) bool {
	targ := r.Sym()
	var targType sym.SymKind
	if targ != 0 {
		targType = ldr.SymType(targ)
	}

	switch r.Type() {
	default:
		if r.Type() >= objabi.ElfRelocOffset {
			ldr.Errorf(s, "unexpected relocation type %d (%s)", r.Type(), sym.RelocName(target.Arch, r.Type()))
			return false
		}

		// 处理ELF对象文件中的重新定位。
	case objabi.ElfRelocOffset + objabi.RelocType(elf.R_PPC64_REL24):
		su := ldr.MakeSymbolUpdater(s)
		su.SetRelocType(rIdx, objabi.R_CALLPOWER)

		// 这是一个本地呼叫，因此呼叫者没有设置
		// 向上r12和r2对于呼叫者和被呼叫者是相同的。因此，我们需要前往当地入口
		// 点。（如果我们不这样做，被叫方将尝试
		// 使用r12计算r2。）
		su.SetRelocAdd(rIdx, r.Add()+int64(ldr.SymLocalentry(targ))*4)

		if targType == sym.SDYNIMPORT {
			// 应该在ELFSETUPLT中处理
			ldr.Errorf(s, "unexpected R_PPC64_REL24 for dyn import")
		}

		return true

	case objabi.ElfRelocOffset + objabi.RelocType(elf.R_PPC_REL32):
		su := ldr.MakeSymbolUpdater(s)
		su.SetRelocType(rIdx, objabi.R_PCREL)
		su.SetRelocAdd(rIdx, r.Add()+4)

		if targType == sym.SDYNIMPORT {
			ldr.Errorf(s, "unexpected R_PPC_REL32 for dyn import")
		}

		return true

	case objabi.ElfRelocOffset + objabi.RelocType(elf.R_PPC64_ADDR64):
		su := ldr.MakeSymbolUpdater(s)
		su.SetRelocType(rIdx, objabi.R_ADDR)
		if targType == sym.SDYNIMPORT {
			// 这些都发生在。toc部分
			ld.Adddynsym(ldr, target, syms, targ)

			rela := ldr.MakeSymbolUpdater(syms.Rela)
			rela.AddAddrPlus(target.Arch, s, int64(r.Off()))
			rela.AddUint64(target.Arch, elf.R_INFO(uint32(ldr.SymDynid(targ)), uint32(elf.R_PPC64_ADDR64)))
			rela.AddUint64(target.Arch, uint64(r.Add()))
			su.SetRelocType(rIdx, objabi.ElfRelocOffset) // 重新连接期间忽略
		} else if target.IsPIE() && target.IsInternal() {
			// 对于内部链接饼图，此地址重新定位无法静态解决
			// 问题。我们需要生成一个动态
			// 重新定位。让下面的代码来处理它。
			break
		}
		return true

	case objabi.ElfRelocOffset + objabi.RelocType(elf.R_PPC64_TOC16):
		su := ldr.MakeSymbolUpdater(s)
		su.SetRelocType(rIdx, objabi.R_POWER_TOC)
		ldr.SetRelocVariant(s, rIdx, sym.RV_POWER_LO|sym.RV_CHECK_OVERFLOW)
		return true

	case objabi.ElfRelocOffset + objabi.RelocType(elf.R_PPC64_TOC16_LO):
		su := ldr.MakeSymbolUpdater(s)
		su.SetRelocType(rIdx, objabi.R_POWER_TOC)
		ldr.SetRelocVariant(s, rIdx, sym.RV_POWER_LO)
		return true

	case objabi.ElfRelocOffset + objabi.RelocType(elf.R_PPC64_TOC16_HA):
		su := ldr.MakeSymbolUpdater(s)
		su.SetRelocType(rIdx, objabi.R_POWER_TOC)
		ldr.SetRelocVariant(s, rIdx, sym.RV_POWER_HA|sym.RV_CHECK_OVERFLOW)
		return true

	case objabi.ElfRelocOffset + objabi.RelocType(elf.R_PPC64_TOC16_HI):
		su := ldr.MakeSymbolUpdater(s)
		su.SetRelocType(rIdx, objabi.R_POWER_TOC)
		ldr.SetRelocVariant(s, rIdx, sym.RV_POWER_HI|sym.RV_CHECK_OVERFLOW)
		return true

	case objabi.ElfRelocOffset + objabi.RelocType(elf.R_PPC64_TOC16_DS):
		su := ldr.MakeSymbolUpdater(s)
		su.SetRelocType(rIdx, objabi.R_POWER_TOC)
		ldr.SetRelocVariant(s, rIdx, sym.RV_POWER_DS|sym.RV_CHECK_OVERFLOW)
		return true

	case objabi.ElfRelocOffset + objabi.RelocType(elf.R_PPC64_TOC16_LO_DS):
		su := ldr.MakeSymbolUpdater(s)
		su.SetRelocType(rIdx, objabi.R_POWER_TOC)
		ldr.SetRelocVariant(s, rIdx, sym.RV_POWER_DS)
		return true

	case objabi.ElfRelocOffset + objabi.RelocType(elf.R_PPC64_REL16_LO):
		su := ldr.MakeSymbolUpdater(s)
		su.SetRelocType(rIdx, objabi.R_PCREL)
		ldr.SetRelocVariant(s, rIdx, sym.RV_POWER_LO)
		su.SetRelocAdd(rIdx, r.Add()+2) // 补偿2的重新定位大小
		return true

	case objabi.ElfRelocOffset + objabi.RelocType(elf.R_PPC64_REL16_HI):
		su := ldr.MakeSymbolUpdater(s)
		su.SetRelocType(rIdx, objabi.R_PCREL)
		ldr.SetRelocVariant(s, rIdx, sym.RV_POWER_HI|sym.RV_CHECK_OVERFLOW)
		su.SetRelocAdd(rIdx, r.Add()+2)
		return true

	case objabi.ElfRelocOffset + objabi.RelocType(elf.R_PPC64_REL16_HA):
		su := ldr.MakeSymbolUpdater(s)
		su.SetRelocType(rIdx, objabi.R_PCREL)
		ldr.SetRelocVariant(s, rIdx, sym.RV_POWER_HA|sym.RV_CHECK_OVERFLOW)
		su.SetRelocAdd(rIdx, r.Add()+2)
		return true
	}

	// 从我们自己的对象文件中处理对ELF符号的引用。
	relocs := ldr.Relocs(s)
	r = relocs.At(rIdx)

	switch r.Type() {
	case objabi.R_ADDR:
		if ldr.SymType(s) == sym.STEXT {
			log.Fatalf("R_ADDR relocation in text symbol %s is unsupported\n", ldr.SymName(s))
		}
		if target.IsPIE() && target.IsInternal() {
			// 内部链接时，为所有典型的R_ADDR重新定位生成动态重新定位
			// 。异常
			// 是作为生成
			// 动态重新定位的一部分而创建的，必须静态解决。
			// 
			// 
			// 
			// dodata（）
			// address（）
			// reloc（）
			// 
			// 此时尚未分配符号地址
			// （由于.rela部分的最终大小将影响地址），因此我们无法编写
			// Elf64_rela。现在就开始。相反，我们将其延迟到链接器的“地址”阶段完成后。我们通过AddRplus实现这一点，它创建了
			// 一个新的R_ADDR重新定位，将在
			// 重新定位阶段解决。
			// 
			// 现在必须跳过这些合成静态R_ADDR relocs 
			// 否则我们将陷入一个无限循环
			// 为我们的合成
			// relocs生成合成relocs。
			// 
			// 此外，rela部分还包含动态
			// 重新定位，并在
			// Elf64上添加重新定位。r_偏移。此字段应包含reloc（）确定的
			// 符号偏移量，而不是作为动态
			// 最终动态链接地址
			// 搬迁将提供。
			switch ldr.SymName(s) {
			case ".dynsym", ".rela", ".rela.plt", ".got.plt", ".dynamic":
				return false
			}
		} else {
			// 内部链接一个静态可执行文件，
			// 在这种情况下，我们可以在'reloc'阶段静态地解决这些重新定位问题，或者外部链接，在这种情况下，重新定位将由
			// 在“重新链接”阶段准备，并在“asmb”阶段传递给
			// 外部链接器。
			if ldr.SymType(s) != sym.SDATA && ldr.SymType(s) != sym.SRODATA {
				break
			}
		}
		// 生成R_PPC64_相对重新定位，以获得最佳
		// 动态链接器的效率。
		// 
		// 如上所述，尚未分配符号地址
		// 因此我们还无法生成最终的reloc 
		// 条目。我们最终想要：
		// 
		// r_offset=s+r。Off 
		// r_info=r_PPC64_RELATIVE 
		// r_addend=target+r。Add 
		// 
		// 动态链接器将设置*offset=base address+
		// addend。
		// 
		// AddAddrPlus用于r_offset和r_addend到
		// 生成新的r_ADDR重定位，该重定位将在'reloc'阶段更新
		// 这些字段。
		rela := ldr.MakeSymbolUpdater(syms.Rela)
		rela.AddAddrPlus(target.Arch, s, int64(r.Off()))
		if r.Siz() == 8 {
			rela.AddUint64(target.Arch, elf.R_INFO(0, uint32(elf.R_PPC64_RELATIVE)))
		} else {
			ldr.Errorf(s, "unexpected relocation for dynamic symbol %s", ldr.SymName(targ))
		}
		rela.AddAddrPlus(target.Arch, targ, int64(r.Add()))

		// 这里没有标记r。所以我们仍然静态地应用它，
		// 所以在文件内容中，我们也将有正确的偏移量
		// 到重新定位目标。因此可以静态检查
		// （例如go版本）。
		return true
	}

	return false
}

func xcoffreloc1(arch *sys.Arch, out *ld.OutBuf, ldr *loader.Loader, s loader.Sym, r loader.ExtReloc, sectoff int64) bool {
	rs := r.Xsym

	emitReloc := func(v uint16, off uint64) {
		out.Write64(uint64(sectoff) + off)
		out.Write32(uint32(ldr.SymDynid(rs)))
		out.Write16(v)
	}

	var v uint16
	switch r.Type {
	default:
		return false
	case objabi.R_ADDR, objabi.R_DWARFSECREF:
		v = ld.XCOFF_R_POS
		if r.Size == 4 {
			v |= 0x1F << 8
		} else {
			v |= 0x3F << 8
		}
		emitReloc(v, 0)
	case objabi.R_ADDRPOWER_TOCREL:
	case objabi.R_ADDRPOWER_TOCREL_DS:
		emitReloc(ld.XCOFF_R_TOCU|(0x0F<<8), 2)
		emitReloc(ld.XCOFF_R_TOCL|(0x0F<<8), 6)
	case objabi.R_POWER_TLS_LE:
		// 这只支持16b重定位。它被安置在archreloc。
		emitReloc(ld.XCOFF_R_TLS_LE|0x0F<<8, 2)
	case objabi.R_CALLPOWER:
		if r.Size != 4 {
			return false
		}
		emitReloc(ld.XCOFF_R_RBR|0x19<<8, 0)
	case objabi.R_XCOFFREF:
		emitReloc(ld.XCOFF_R_REF|0x3F<<8, 0)
	}
	return true

}

func elfreloc1(ctxt *ld.Link, out *ld.OutBuf, ldr *loader.Loader, s loader.Sym, r loader.ExtReloc, ri int, sectoff int64) bool {
	// 注意，在Big-Endian机器中，bit0~bit15从指令的第三个字节开始。
	rt := r.Type
	if rt == objabi.R_ADDR || rt == objabi.R_POWER_TLS || rt == objabi.R_CALLPOWER {
	} else {
		if ctxt.Arch.ByteOrder == binary.BigEndian {
			sectoff += 2
		}
	}
	out.Write64(uint64(sectoff))

	elfsym := ld.ElfSymForReloc(ctxt, r.Xsym)
	switch rt {
	default:
		return false
	case objabi.R_ADDR, objabi.R_DWARFSECREF:
		switch r.Size {
		case 4:
			out.Write64(uint64(elf.R_PPC64_ADDR32) | uint64(elfsym)<<32)
		case 8:
			out.Write64(uint64(elf.R_PPC64_ADDR64) | uint64(elfsym)<<32)
		default:
			return false
		}
	case objabi.R_POWER_TLS:
		out.Write64(uint64(elf.R_PPC64_TLS) | uint64(elfsym)<<32)
	case objabi.R_POWER_TLS_LE:
		out.Write64(uint64(elf.R_PPC64_TPREL16_HA) | uint64(elfsym)<<32)
		out.Write64(uint64(r.Xadd))
		out.Write64(uint64(sectoff + 4))
		out.Write64(uint64(elf.R_PPC64_TPREL16_LO) | uint64(elfsym)<<32)
	case objabi.R_POWER_TLS_IE:
		out.Write64(uint64(elf.R_PPC64_GOT_TPREL16_HA) | uint64(elfsym)<<32)
		out.Write64(uint64(r.Xadd))
		out.Write64(uint64(sectoff + 4))
		out.Write64(uint64(elf.R_PPC64_GOT_TPREL16_LO_DS) | uint64(elfsym)<<32)
	case objabi.R_ADDRPOWER:
		out.Write64(uint64(elf.R_PPC64_ADDR16_HA) | uint64(elfsym)<<32)
		out.Write64(uint64(r.Xadd))
		out.Write64(uint64(sectoff + 4))
		out.Write64(uint64(elf.R_PPC64_ADDR16_LO) | uint64(elfsym)<<32)
	case objabi.R_ADDRPOWER_DS:
		out.Write64(uint64(elf.R_PPC64_ADDR16_HA) | uint64(elfsym)<<32)
		out.Write64(uint64(r.Xadd))
		out.Write64(uint64(sectoff + 4))
		out.Write64(uint64(elf.R_PPC64_ADDR16_LO_DS) | uint64(elfsym)<<32)
	case objabi.R_ADDRPOWER_GOT:
		out.Write64(uint64(elf.R_PPC64_GOT16_HA) | uint64(elfsym)<<32)
		out.Write64(uint64(r.Xadd))
		out.Write64(uint64(sectoff + 4))
		out.Write64(uint64(elf.R_PPC64_GOT16_LO_DS) | uint64(elfsym)<<32)
	case objabi.R_ADDRPOWER_PCREL:
		out.Write64(uint64(elf.R_PPC64_REL16_HA) | uint64(elfsym)<<32)
		out.Write64(uint64(r.Xadd))
		out.Write64(uint64(sectoff + 4))
		out.Write64(uint64(elf.R_PPC64_REL16_LO) | uint64(elfsym)<<32)
		r.Xadd += 4
	case objabi.R_ADDRPOWER_TOCREL:
		out.Write64(uint64(elf.R_PPC64_TOC16_HA) | uint64(elfsym)<<32)
		out.Write64(uint64(r.Xadd))
		out.Write64(uint64(sectoff + 4))
		out.Write64(uint64(elf.R_PPC64_TOC16_LO) | uint64(elfsym)<<32)
	case objabi.R_ADDRPOWER_TOCREL_DS:
		out.Write64(uint64(elf.R_PPC64_TOC16_HA) | uint64(elfsym)<<32)
		out.Write64(uint64(r.Xadd))
		out.Write64(uint64(sectoff + 4))
		out.Write64(uint64(elf.R_PPC64_TOC16_LO_DS) | uint64(elfsym)<<32)
	case objabi.R_CALLPOWER:
		if r.Size != 4 {
			return false
		}
		out.Write64(uint64(elf.R_PPC64_REL24) | uint64(elfsym)<<32)

	}
	out.Write64(uint64(r.Xadd))

	return true
}

func elfsetupplt(ctxt *ld.Link, plt, got *loader.SymbolBuilder, dynamic loader.Sym) {
	if plt.Size() == 0 {
		// 动态链接器将
		// 动态解析程序的地址和DSO标识符存储在两个
		// 双字的开头。plt段
		// 在plt阵列之前。为这些预留空间。
		plt.SetSize(16)
	}
}

func machoreloc1(*sys.Arch, *ld.OutBuf, *loader.Loader, loader.Sym, loader.ExtReloc, int64) bool {
	return false
}

// 返回的值。TOC。对于符号s 
func symtoc(ldr *loader.Loader, syms *ld.ArchSyms, s loader.Sym) int64 {
	v := ldr.SymVersion(s)
	if out := ldr.OuterSym(s); out != 0 {
		v = ldr.SymVersion(out)
	}

	toc := syms.DotTOC[v]
	if toc == 0 {
		ldr.Errorf(s, "TOC-relative relocation in object without .TOC.")
		return 0
	}

	return ldr.SymValue(toc)
}

// archreloctoc重新定位TOC相对符号。
func archreloctoc(ldr *loader.Loader, target *ld.Target, syms *ld.ArchSyms, r loader.Reloc, s loader.Sym, val int64) int64 {
	rs := r.Sym()
	var o1, o2 uint32
	var t int64
	useAddi := false

	if target.IsBigEndian() {
		o1 = uint32(val >> 32)
		o2 = uint32(val)
	} else {
		o1 = uint32(val)
		o2 = uint32(val >> 32)
	}

	// 在AIX上，TOC数据访问总是间接针对R2（一系列addis+ld+load/store）。如果
	// 加载目标已知，则可以将序列写入addis+addi+load/store。在Linux上，
	// TOC数据访问总是直接针对R2（例如addis+load/store）。
	if target.IsAIX() {
		if !strings.HasPrefix(ldr.SymName(rs), "TOC.") {
			ldr.Errorf(s, "archreloctoc called for a symbol without TOC anchor")
		}
		relocs := ldr.Relocs(rs)
		tarSym := relocs.At(0).Sym()

		if target.IsInternal() && tarSym != 0 && ldr.AttrReachable(tarSym) && ldr.SymSect(tarSym).Seg == &ld.Segdata {
			t = ldr.SymValue(tarSym) + r.Add() - ldr.SymValue(syms.TOC)
			// 将第二条指令中的ld改为addi 
			o2 = (o2 & 0x03FF0000) | 0xE<<26
			useAddi = true
		} else {
			t = ldr.SymValue(rs) + r.Add() - ldr.SymValue(syms.TOC)
		}
	} else {
		t = ldr.SymValue(rs) + r.Add() - symtoc(ldr, syms, s)
	}

	if t != int64(int32(t)) {
		ldr.Errorf(s, "TOC relocation for %s is too big to relocate %s: 0x%x", ldr.SymName(s), rs, t)
	}

	if t&0x8000 != 0 {
		t += 0x10000
	}

	o1 |= uint32((t >> 16) & 0xFFFF)

	switch r.Type() {
	case objabi.R_ADDRPOWER_TOCREL_DS:
		if useAddi {
			o2 |= uint32(t) & 0xFFFF
		} else {
			if t&3 != 0 {
				ldr.Errorf(s, "bad DS reloc for %s: %d", ldr.SymName(s), ldr.SymValue(rs))
			}
			o2 |= uint32(t) & 0xFFFC
		}
	case objabi.R_ADDRPOWER_TOCREL:
		o2 |= uint32(t) & 0xffff
	default:
		return -1
	}

	if target.IsBigEndian() {
		return int64(o1)<<32 | int64(o2)
	}
	return int64(o2)<<32 | int64(o1)
}

// archrelocaddr重新定位符号地址。
// 此代码仅适用于linux。
func archrelocaddr(ldr *loader.Loader, target *ld.Target, syms *ld.ArchSyms, r loader.Reloc, s loader.Sym, val int64) int64 {
	rs := r.Sym()
	if target.IsAIX() {
		ldr.Errorf(s, "archrelocaddr called for %s relocation\n", ldr.SymName(rs))
	}
	var o1, o2 uint32
	if target.IsBigEndian() {
		o1 = uint32(val >> 32)
		o2 = uint32(val)
	} else {
		o1 = uint32(val)
		o2 = uint32(val >> 32)
	}

	// 我们将一个31位地址分布在两条指令上，将
	// 高（调整）部分放在第一条指令的低16位，将
	// 低部分放在第二条指令的低16位，或者在DS情况下，
	// 将地址的第15-2位（含15-2位）转换为第二条
	// 指令的第15-2位（如果地址
	// 的低位2位非零，则在这种情况下是错误的）。

	t := ldr.SymAddr(rs) + r.Add()
	if t < 0 || t >= 1<<31 {
		ldr.Errorf(s, "relocation for %s is too big (>=2G): 0x%x", ldr.SymName(s), ldr.SymValue(rs))
	}
	if t&0x8000 != 0 {
		t += 0x10000
	}

	switch r.Type() {
	case objabi.R_ADDRPOWER:
		o1 |= (uint32(t) >> 16) & 0xffff
		o2 |= uint32(t) & 0xffff
	case objabi.R_ADDRPOWER_DS:
		o1 |= (uint32(t) >> 16) & 0xffff
		if t&3 != 0 {
			ldr.Errorf(s, "bad DS reloc for %s: %d", ldr.SymName(s), ldr.SymValue(rs))
		}
		o2 |= uint32(t) & 0xfffc
	default:
		return -1
	}

	if target.IsBigEndian() {
		return int64(o1)<<32 | int64(o2)
	}
	return int64(o2)<<32 | int64(o1)
}

// 确定代码的编译是否使TOC寄存器R2得到初始化和维护
func r2Valid(ctxt *ld.Link) bool {
	switch ctxt.BuildMode {
	case ld.BuildModeCArchive, ld.BuildModeCShared, ld.BuildModePIE, ld.BuildModeShared, ld.BuildModePlugin:
		return true
	}
	// /-linkshared选项
	return ctxt.IsSharedGoLink()
}

// 解决s中的直接跳转重定位，并在必要时添加蹦床
func trampoline(ctxt *ld.Link, ldr *loader.Loader, ri int, rs, s loader.Sym) {

	// 如果分支偏移太大，链接器无法插入调用存根来处理它，则会创建蹦床。
	// 对于内部连接，蹦床总是为长通话创建的。
	// 对于外部链接，链接器可以插入呼叫存根来处理长呼叫，但这取决于在
	// r2中有TOC地址。对于那些带有外部链接的构建模式，在r2中不维护TOC地址的情况下，必须创建蹦床。
	if ctxt.IsExternal() && r2Valid(ctxt) {
		// 不需要蹦床，因为r2包含TOC 
		return
	}

	relocs := ldr.Relocs(s)
	r := relocs.At(ri)
	var t int64
	// ldr。SymValue（rs）==0表示跨包跳转到尚未部署的函数
	// 中。保守地使用蹦床。这应该是很少见的，因为我们按照依赖关系顺序排列包
	// 。
	if ldr.SymValue(rs) != 0 {
		t = ldr.SymValue(rs) + r.Add() - (ldr.SymValue(s) + int64(r.Off()))
	}
	switch r.Type() {
	case objabi.R_CALLPOWER:

		// 如果分支偏移太远，则创建蹦床。

		if (ctxt.IsExternal() && ldr.SymSect(s) != ldr.SymSect(rs)) || (ctxt.IsInternal() && int64(int32(t<<6)>>6) != t) || ldr.SymValue(rs) == 0 || (*ld.FlagDebugTramp > 1 && ldr.SymPkg(s) != ldr.SymPkg(rs)) {
			var tramp loader.Sym
			for i := 0; ; i++ {

				// 在像duffzero这样的函数中，使用r.Add作为名称的一部分是很重要的，其中调用
				// target位于函数中的某个偏移量。对达夫+8和达夫+256的呼叫必须显示为
				// 不同的蹦床。

				oName := ldr.SymName(rs)
				name := oName
				if r.Add() == 0 {
					name += fmt.Sprintf("-tramp%d", i)
				} else {
					name += fmt.Sprintf("%+x-tramp%d", r.Add(), i)
				}

				// 如果蹦床已经存在，请查找蹦床

				tramp = ldr.LookupOrCreateSym(name, int(ldr.SymVersion(rs)))
				if oName == "runtime.deferreturn" {
					ldr.SetIsDeferReturnTramp(tramp, true)
				}
				if ldr.SymValue(tramp) == 0 {
					break
				}

				t = ldr.SymValue(tramp) + r.Add() - (ldr.SymValue(s) + int64(r.Off()))

				// 带有内部链接，如果不太远，可以使用蹦床。
				// 通过外部连接，蹦床必须在本节中才能重复使用。
				if (ctxt.IsInternal() && int64(int32(t<<6)>>6) == t) || (ctxt.IsExternal() && ldr.SymSect(s) == ldr.SymSect(tramp)) {
					break
				}
			}
			if ldr.SymType(tramp) == 0 {
				if r2Valid(ctxt) {
					// 上述情况下应该返回
					ctxt.Errorf(s, "unexpected trampoline for shared or dynamic linking")
				} else {
					trampb := ldr.MakeSymbolUpdater(tramp)
					ctxt.AddTramp(trampb)
					gentramp(ctxt, ldr, trampb, rs, r.Add())
				}
			}
			sb := ldr.MakeSymbolUpdater(s)
			relocs := sb.Relocs()
			r := relocs.At(ri)
			r.SetSym(tramp)
			r.SetAdd(0) // 这是折叠到蹦床目标地址
		}
	default:
		ctxt.Errorf(s, "trampoline called with non-jump reloc: %d (%s)", r.Type(), sym.RelocName(ctxt.Arch, r.Type()))
	}
}

func gentramp(ctxt *ld.Link, ldr *loader.Loader, tramp *loader.SymbolBuilder, target loader.Sym, offset int64) {
	tramp.SetSize(16) // 4指令
	P := make([]byte, tramp.Size())
	t := ldr.SymValue(target) + offset
	var o1, o2 uint32

	if ctxt.IsAIX() {
		// 在AIX上，使用TOC符号检索地址。
		// 对于内部链接，可能仍然使用“Linux”方式。
		// 然而，所有文本符号都是通过TOC符号访问的，因为
		// 文本重新定位不可能。
		// 所以，继续使用外部链接方式，以更友好地使用AIX。
		o1 = uint32(0x3fe20000) // lis r2，TOCTargetAddress hi 
		o2 = uint32(0xebff0000) // ld r31，TOCTargetAddress lo 

		toctramp := ldr.CreateSymForUpdate("TOC."+ldr.SymName(tramp.Sym()), 0)
		toctramp.SetType(sym.SXCOFFTOC)
		toctramp.AddAddrPlus(ctxt.Arch, target, offset)

		r, _ := tramp.AddRel(objabi.R_ADDRPOWER_TOCREL_DS)
		r.SetOff(0)
		r.SetSiz(8) // 生成2个重定位：HA+lo 
		r.SetSym(toctramp.Sym())
	} else {
		// 用于可执行
		// 调用目标的地址是使用
		// 重定位生成的，不依赖于r2（TOC）。
		o1 = uint32(0x3fe00000) // lis r31，targetaddr hi 
		o2 = uint32(0x3bff0000) // addi r31，targetaddr lo 

		// ，目标地址必须是
		// 使用LO和HA重新定位
		if ctxt.IsExternal() || ldr.SymValue(target) == 0 {
			r, _ := tramp.AddRel(objabi.R_ADDRPOWER)
			r.SetOff(0)
			r.SetSiz(8) // 生成2个重新定位：HA+LO 
			r.SetSym(target)
			r.SetAdd(offset)
		} else {
			// 如果LO设置了符号位
			// 使用addi计算地址
			val := uint32((t & 0xffff0000) >> 16)
			if t&0x8000 != 0 {
				val += 1
			}
			o1 |= val                // hi部分addr 
			o2 |= uint32(t & 0xffff) // LO部分addr 
		}
	}

	o3 := uint32(0x7fe903a6) // mtctr r31 
	o4 := uint32(0x4e800420) // bctr 
	ctxt.Arch.ByteOrder.PutUint32(P, o1)
	ctxt.Arch.ByteOrder.PutUint32(P[4:], o2)
	ctxt.Arch.ByteOrder.PutUint32(P[8:], o3)
	ctxt.Arch.ByteOrder.PutUint32(P[12:], o4)
	tramp.SetData(P)
}

func archreloc(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, r loader.Reloc, s loader.Sym, val int64) (relocatedOffset int64, nExtReloc int, ok bool) {
	rs := r.Sym()
	if target.IsExternal() {
		// 在AIX上，还必须使用当前地址对
		// 值进行重新定位（TLS除外）。
		switch rt := r.Type(); rt {
		default:
			if !target.IsAIX() {
				return val, nExtReloc, false
			}
		case objabi.R_POWER_TLS:
			nExtReloc = 1
			return val, nExtReloc, true
		case objabi.R_POWER_TLS_LE, objabi.R_POWER_TLS_IE:
			if target.IsAIX() && rt == objabi.R_POWER_TLS_LE {
				// Fixup val是一对ADIS/addi指令，它从threadpointer（R13）生成32b位移
				// ，并将其重新定位为16b。XCOFF仅支持16b 
				// TLS LE重定位。同样，验证这是一个addis/addi序列。
				const expectedOpcodes = 0x3C00000038000000
				const expectedOpmasks = 0xFC000000FC000000
				if uint64(val)&expectedOpmasks != expectedOpcodes {
					ldr.Errorf(s, "relocation for %s+%d is not an addis/addi pair: %16x", ldr.SymName(rs), r.Off(), uint64(val))
				}
				nval := (int64(uint32(0x380d0000)) | val&0x03e00000) << 32 // addi rX，r13，$0 
				nval |= int64(0x60000000)                                  // nop 
				val = nval
				nExtReloc = 1
			} else {
				nExtReloc = 2
			}
			return val, nExtReloc, true
		case objabi.R_ADDRPOWER,
			objabi.R_ADDRPOWER_DS,
			objabi.R_ADDRPOWER_TOCREL,
			objabi.R_ADDRPOWER_TOCREL_DS,
			objabi.R_ADDRPOWER_GOT,
			objabi.R_ADDRPOWER_PCREL:
			nExtReloc = 2 // 需要两个ELF重定位，请参阅elfreloc1 
			if !target.IsAIX() {
				return val, nExtReloc, true
			}
		case objabi.R_CALLPOWER:
			nExtReloc = 1
			if !target.IsAIX() {
				return val, nExtReloc, true
			}
		}
	}

	switch r.Type() {
	case objabi.R_ADDRPOWER_TOCREL, objabi.R_ADDRPOWER_TOCREL_DS:
		return archreloctoc(ldr, target, syms, r, s, val), nExtReloc, true
	case objabi.R_ADDRPOWER, objabi.R_ADDRPOWER_DS:
		return archrelocaddr(ldr, target, syms, r, s, val), nExtReloc, true
	case objabi.R_CALLPOWER:
		// 位6到29=（S+A-P）>>2 

		t := ldr.SymValue(rs) + r.Add() - (ldr.SymValue(s) + int64(r.Off()))

		tgtName := ldr.SymName(rs)

		// 如果我们链接饼图或共享代码，所有golang生成的对象文件都有一个额外的2指令序言
		// 从R12重新生成TOC指针。例外情况是下面测试的两个特殊情况函数。注意，
		// 在转换为golang relocs时，会考虑外部生成对象的本地调用偏移量。xiang jian defg
		if !ldr.IsExternal(rs) && ldr.AttrShared(rs) && tgtName != "runtime.duffzero" && tgtName != "runtime.duffcopy" {
			if r.Add() == 0 && ldr.SymType(rs) == sym.STEXT {
				t += 8
			}
		}

		if t&3 != 0 {
			ldr.Errorf(s, "relocation for %s+%d is not aligned: %d", ldr.SymName(rs), r.Off(), t)
		}
		// 如果分支偏移太远，则创建蹦床。

		if int64(int32(t<<6)>>6) != t {
			ldr.Errorf(s, "direct call too far: %s %x", ldr.SymName(rs), t)
		}
		return val | int64(uint32(t)&^0xfc000003), nExtReloc, true
	case objabi.R_POWER_TOC: // S+A-。TOC。
		return ldr.SymValue(rs) + r.Add() - symtoc(ldr, syms, s), nExtReloc, true

	case objabi.R_ADDRPOWER_PCREL: // S+A-P 
		t := ldr.SymValue(rs) + r.Add() - (ldr.SymValue(s) + int64(r.Off()))
		ha := uint16(((t + 0x8000) >> 16) & 0xFFFF)
		l := uint16(t)
		if target.IsBigEndian() {
			val |= int64(l)
			val |= int64(ha) << 32
		} else {
			val |= int64(ha)
			val |= int64(l) << 32
		}
		return val, nExtReloc, true

	case objabi.R_POWER_TLS:
		const OP_ADD = 31<<26 | 266<<1
		const MASK_OP_ADD = 0x3F<<26 | 0x1FF<<1
		if val&MASK_OP_ADD != OP_ADD {
			ldr.Errorf(s, "R_POWER_TLS reloc only supports XO form ADD, not %08X", val)
		}
		// 在添加RA、RB、RT时验证RB是R13。
		if (val>>11)&0x1F != 13 {
			// 如果外部链接支持此功能，则可能会要求链接器重写RB。
			ldr.Errorf(s, "R_POWER_TLS reloc requires R13 in RB (%08X).", uint32(val))
		}
		return val, nExtReloc, true

	case objabi.R_POWER_TLS_IE:
		// 如果支持，将TLS_IE重新定位转换为TLS_LE。
		if !(target.IsPIE() && target.IsElf()) {
			log.Fatalf("cannot handle R_POWER_TLS_IE (sym %s) when linking non-PIE, non-ELF binaries internally", ldr.SymName(s))
		}

		// 我们是一个ELF二进制文件，我们可以安全地将其转换为TLS_LE from:
		// addis to，r2，x@got@tprel@ha
		// ld to，to，x@got@tprel@l（至）
		// 
		// ，x@tprel@ha 
		// ，x@tprel@l（to）

		const OP_ADDI = 14 << 26
		const OP_MASK = 0x3F << 26
		const OP_RA_MASK = 0x1F << 16
		uval := uint64(val)
		// 将r2转换为r0，将ld转换为addi 
		if target.IsBigEndian() {
			uval = uval &^ (OP_RA_MASK << 32)
			uval = (uval &^ OP_MASK) | OP_ADDI
		} else {
			uval = uval &^ (OP_RA_MASK)
			uval = (uval &^ (OP_MASK << 32)) | (OP_ADDI << 32)
		}
		val = int64(uval)
		// 现在就把这当作一次重新安置。
		fallthrough

	case objabi.R_POWER_TLS_LE:
		// 线程指针指向
		// 线程本地存储区开始后的0x7000字节，如“Power Architecture 64位ELF V2 ABI 
		// 运行时处理”一节所述。
		// 规范”的“3.7.2 TLS 
		v := ldr.SymValue(rs) - 0x7000
		if target.IsAIX() {
			// 在AIX上，线程指针指向TLS后面的0x7800字节。
			v -= 0x800
		}

		var o1, o2 uint32
		if int64(int32(v)) != v {
			ldr.Errorf(s, "TLS offset out of range %d", v)
		}
		if target.IsBigEndian() {
			o1 = uint32(val >> 32)
			o2 = uint32(val)
		} else {
			o1 = uint32(val)
			o2 = uint32(val >> 32)
		}

		o1 |= uint32(((v + 0x8000) >> 16) & 0xFFFF)
		o2 |= uint32(v & 0xFFFF)

		if target.IsBigEndian() {
			return int64(o1)<<32 | int64(o2), nExtReloc, true
		}
		return int64(o2)<<32 | int64(o1), nExtReloc, true
	}

	return val, nExtReloc, false
}

func archrelocvariant(target *ld.Target, ldr *loader.Loader, r loader.Reloc, rv sym.RelocVariant, s loader.Sym, t int64, p []byte) (relocatedOffset int64) {
	rs := r.Sym()
	switch rv & sym.RV_TYPE_MASK {
	default:
		ldr.Errorf(s, "unexpected relocation variant %d", rv)
		fallthrough

	case sym.RV_NONE:
		return t

	case sym.RV_POWER_LO:
		if rv&sym.RV_CHECK_OVERFLOW != 0 {
			// 是否检查有符号或无符号
			// 溢出取决于指令
			var o1 uint32
			if target.IsBigEndian() {
				o1 = binary.BigEndian.Uint32(p[r.Off()-2:])

			} else {
				o1 = binary.LittleEndian.Uint32(p[r.Off():])
			}
			switch o1 >> 26 {
			case 24, // ori 
				26, // xori 
				28: // andi 
				if t>>16 != 0 {
					goto overflow
				}

			default:
				if int64(int16(t)) != t {
					goto overflow
				}
			}
		}

		return int64(int16(t))

	case sym.RV_POWER_HA:
		t += 0x8000
		fallthrough

		// 故障
	case sym.RV_POWER_HI:
		t >>= 16

		if rv&sym.RV_CHECK_OVERFLOW != 0 {
			// 是否检查有符号或无符号
			// 溢出取决于指令
			var o1 uint32
			if target.IsBigEndian() {
				o1 = binary.BigEndian.Uint32(p[r.Off()-2:])
			} else {
				o1 = binary.LittleEndian.Uint32(p[r.Off():])
			}
			switch o1 >> 26 {
				29: // andis 
				if t>>16 != 0 {
					goto overflow
				}

			default:
				if int64(int16(t)) != t {
					goto overflow
				}
			}
		}

		return int64(int16(t))

	case sym.RV_POWER_DS:
		var o1 uint32
		if target.IsBigEndian() {
			o1 = uint32(binary.BigEndian.Uint16(p[r.Off():]))
		} else {
			o1 = uint32(binary.LittleEndian.Uint16(p[r.Off():]))
		}
		if t&3 != 0 {
			ldr.Errorf(s, "relocation for %s+%d is not aligned: %d", ldr.SymName(rs), r.Off(), t)
		}
		if (rv&sym.RV_CHECK_OVERFLOW != 0) && int64(int16(t)) != t {
			goto overflow
		}
		return int64(o1)&0x3 | int64(int16(t))
	}

overflow:
	ldr.Errorf(s, "relocation for %s+%d is too big: %d", ldr.SymName(rs), r.Off(), t)
	return t
}

func extreloc(target *ld.Target, ldr *loader.Loader, r loader.Reloc, s loader.Sym) (loader.ExtReloc, bool) {
	switch r.Type() {
	case objabi.R_POWER_TLS, objabi.R_POWER_TLS_LE, objabi.R_POWER_TLS_IE, objabi.R_CALLPOWER:
		return ld.ExtrelocSimple(ldr, r), true
	case objabi.R_ADDRPOWER,
		objabi.R_ADDRPOWER_DS,
		objabi.R_ADDRPOWER_TOCREL,
		objabi.R_ADDRPOWER_TOCREL_DS,
		objabi.R_ADDRPOWER_GOT,
		objabi.R_ADDRPOWER_PCREL:
		return ld.ExtrelocViaOuterSym(ldr, r, s), true
	}
	return loader.ExtReloc{}, false
}

func addpltsym(ctxt *ld.Link, ldr *loader.Loader, s loader.Sym) {
	if ldr.SymPlt(s) >= 0 {
		return
	}

	ld.Adddynsym(ldr, &ctxt.Target, &ctxt.ArchSyms, s)

	if ctxt.IsELF {
		plt := ldr.MakeSymbolUpdater(ctxt.PLT)
		rela := ldr.MakeSymbolUpdater(ctxt.RelaPLT)
		if plt.Size() == 0 {
			panic("plt is not set up")
		}

		// 必要时创建glink解析器
		glink := ensureglinkresolver(ctxt, ldr)

		// 写入符号解析器存根（只是
		// glink解析器存根的一个分支）
		rel, _ := glink.AddRel(objabi.R_CALLPOWER)
		rel.SetOff(int32(glink.Size()))
		rel.SetSiz(4)
		rel.SetSym(glink.Sym())
		glink.AddUint32(ctxt.Arch, 0x48000000) // b。glink 

		// 在ppc64 ABI中，动态链接器负责
		// 写入整个PLT。我们只需要为每个PLT条目保留8个字节，并为其生成一个
		// JMP_插槽动态重定位。
		// 
		// TODO（奥斯汀）：ABI v1与
		ldr.SetPlt(s, int32(plt.Size()))

		plt.Grow(plt.Size() + 8)
		plt.SetSize(plt.Size() + 8)

		rela.AddAddrPlus(ctxt.Arch, plt.Sym(), int64(ldr.SymPlt(s)))
		rela.AddUint64(ctxt.Arch, elf.R_INFO(uint32(ldr.SymDynid(s)), uint32(elf.R_PPC64_JMP_SLOT)))
		rela.AddUint64(ctxt.Arch, 0)
	} else {
		ctxt.Errorf(s, "addpltsym: unsupported binary format")
	}
}

// 如果需要，生成glink解析器存根并返回。glink section 
func ensureglinkresolver(ctxt *ld.Link, ldr *loader.Loader) *loader.SymbolBuilder {
	glink := ldr.CreateSymForUpdate(".glink", 0)
	if glink.Size() != 0 {
		return glink
	}

	// 这基本上是ppc64 ELFv2 ABI的解析器。
	// 在条目中，r12保存目标例程的符号解析程序存根的地址，参数寄存器保存
	// 目标例程的参数。
	// 
	// PC rel偏移量在已知
	// 解析器的最终代码大小后计算。
	// 
	// 这个存根是PIC，所以首先将标签1的PC放入r11。
	glink.AddUint32(ctxt.Arch, 0x7c0802a6) // mflr r0 
	glink.AddUint32(ctxt.Arch, 0x429f0005) // bcl 20,31,1f 
	glink.AddUint32(ctxt.Arch, 0x7d6802a6) // 1:mflr r11 
	glink.AddUint32(ctxt.Arch, 0x7c0803a6) // mtlr r0 

	// 。plt数组索引从入口点地址
	// 到r0。这是相对于上面的标签1计算的。
	glink.AddUint32(ctxt.Arch, 0x38000000) // li r0，-（res_0-1b）
	glink.AddUint32(ctxt.Arch, 0x7c006214) // add r0，r0，r12 
	glink.AddUint32(ctxt.Arch, 0x7c0b0050) // sub r0，r0，r11 
	glink.AddUint32(ctxt.Arch, 0x7800f082) // srdi r0，r0，2 

	// 加载“.plt-1b”的PC相对偏移量，并将其添加到1b。
	// 这存储在这个存根之后和解析程序之前。
	glink.AddUint32(ctxt.Arch, 0xe98b0000) // ld r12，res_0-1b-8（r11）
	glink.AddUint32(ctxt.Arch, 0x7d6b6214) // add r11，r11，r12 

	// Load r12=动态解析程序地址，r11=DSO 
	// PLT前两个双字的标识符。
	glink.AddUint32(ctxt.Arch, 0xe98b0000) // ld r12,0（r11）
	glink.AddUint32(ctxt.Arch, 0xe96b0008) // ld r11,8（r11）

	// 跳转到动态解析程序
	glink.AddUint32(ctxt.Arch, 0x7d8903a6) // mtctr r12 
	glink.AddUint32(ctxt.Arch, 0x4e800420) // bctr 

	// 将PC的相对偏移量存储到PLT 
	r, _ := glink.AddRel(objabi.R_PCREL)
	r.SetSym(ctxt.PLT)
	r.SetSiz(8)
	r.SetOff(int32(glink.Size()))
	r.SetAdd(glink.Size())        // 调整偏移量，使其与上面的标签1相对。
	glink.AddUint64(ctxt.Arch, 0) // PLT的偏移量。

	// 现在已知存根的最终大小，请解决上面的PC rel偏移。
	res0m1b := glink.Size() - 8 // res_0-1b 
	glink.SetUint32(ctxt.Arch, 16, 0x38000000|uint32(uint16(-res0m1b)))
	glink.SetUint32(ctxt.Arch, 32, 0xe98b0000|uint32(uint16(res0m1b-8)))

	// 符号解析程序必须立即跟随。
	// res_0:

	// 添加DT_PPC64_GLINK。动态条目，指向第一个符号解析程序存根之前的32字节
	// 。
	du := ldr.MakeSymbolUpdater(ctxt.Dynamic)
	ld.Elfwritedynentsymplus(ctxt, du, elf.DT_PPC64_GLINK, glink.Sym(), glink.Size()-32)

	return glink
}
